import hashlib
import os
import tarfile
import zipfile

import pandas as pd
import requests
import torch

DATA_HUB = dict()
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'


def download(name, cache_dir=os.path.join('..', 'data')):
    '''下载一个DATA_HUB中的文件，返回本地文件名'''
    # assert后面是不满足条件时显示的字符串，如果想执行操作就try except
    assert name in DATA_HUB, f"{name} 不存在于 {DATA_HUB}."
    # 根据传入的name获取url和密钥
    url, sha1_hash = DATA_HUB[name]
    os.makedirs(cache_dir, exist_ok=True)
    # 将url按 '/' 切五花肉 获取五花肉块列表 返回最后一块肉
    fname = os.path.join(cache_dir, url.split('/')[-1])

    if os.path.exists(fname):
        # 创建一个sha1加密的对象
        sha1 = hashlib.sha1()
        with open(fname, 'rb') as f:
            while True:
                data = f.read(1048576)  # 2的20次方bytes（字节） 即1MB
                if not data:
                    break  # 不用再判断密钥了直接给我去下载吧
                sha1.update(data)
        # 如果生成的密钥和之前DATA_HUB中保存的秘钥一样那么就直接返回其文件名，不用下载
        if sha1.hexdigest() == sha1_hash:
            print('Nep！文件已存在！')
            return fname

    # 那就只有下载了噻
    print(f'Nep！正在从{url}中下载{fname}...')
    r = requests.get(url, stream=True, verify=True)
    with open(fname, 'wb') as f:
        f.write(r.content)
    return fname


def download_extract(name, folder=None):
    '''下载并解压zip/tar文件'''
    fname = download(name)
    base_dir = os.path.dirname(fname)  # 去掉文件名，返回目录base_dir
    data_dir, ext = os.path.splitext(fname)  # 获取文件名和后缀名
    if ext == '.zip':
        fp = zipfile.ZipFile(fname, 'r')
    elif ext in ('.tar', '.gz'):
        fp = tarfile.open(fname, 'r')
    else:
        assert False, 'Nep! 只可以解压zip/tar哦！'
    fp.extractall(base_dir)  # 解压到当前目录base_dir
    return os.path.join(base_dir, folder) if folder else data_dir


def download_all():
    '''下载DATA_HUB中的所有文件'''
    for name in DATA_HUB:
        download(name)


# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
    print('----------')
    # 1-定义要下载的文件
    DATA_HUB['kaggle_house_train'] = (
        # 二元组分别对应其url和密钥
        DATA_URL + 'kaggle_house_pred_train.csv',
        '585e9cc93e70b39160e7921475f9bcd7d31219ce')

    DATA_HUB['kaggle_house_test'] = (
        DATA_URL + 'kaggle_house_pred_test.csv',
        'fa19780a7b011d9b009e8bff8e99922a8ee2eb90')

    # 2-进行文件下载
    train_data = pd.read_csv(download('kaggle_house_train'))
    test_data = pd.read_csv(download('kaggle_house_test'))

    # 3-查看数据格式
    print('----------train_data.shape：', train_data.shape)
    print('----------test_data.shape：', test_data.shape)

    # 4-查看前四个样本的前四个后最后两个特征，以及相应的标价
    print('----------train_data.iloc：', train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]])

    # 5-【数据预处理】我们将其id信息从数据集中删除
    # 去掉ID后的所有样本,都表示对于训练集/测试集的所有样本，从下标1一直到最后，使用pd的concat将二者合并
    all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
    print('----------all_features.shape：', all_features.shape)

    # 6-【数据预处理-数值特征】将所有缺失值替换为相应特征的平均值。然后进行feature scaling（先标准化再替换会更方便，因为标准化后均值就是0了）
    # 假设不是'object'类的数据就是数值类，把所有数值特征提取出来
    numeric_feaures = all_features.dtypes[all_features.dtypes != 'object'].index
    # 对数值特征进行标准化(实际情况拿不到测试集的时候可以用训练集的均值方差代替)
    all_features[numeric_feaures] = all_features[numeric_feaures].apply(
        lambda x: (x - x.mean()) / (x.std()))
    # 把NAN变成0
    all_features[numeric_feaures] = all_features[numeric_feaures].fillna(0)

    # 7-【数据预处理-离散值特征】进行one-hot独热编码替换
    all_features = pd.get_dummies(all_features, dummy_na=True)
    # 将布尔值转换为0和1(当前Python版本默认使用True/False)
    all_features = all_features.astype(int)

    # 8-【数据预处理-分割数据集】将数据集分割为训练集和测试集
    n_train = train_data.shape[0]
    # 从all_features中取出前n_train行作为训练集，后n_test行作为测试集
    train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float32)
    test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float32)
    # 从train_data中取出前n_train行作为训练集，后n_test行作为测试集
    train_labels = torch.tensor(train_data.SalePrice.values, dtype=torch.float32).view(-1, 1)

    # 9-【数据预处理-查看数据】查看数据
    print('----------train_features.shape：', train_features.shape)
    print('----------train_labels.shape：', train_labels.shape)
    print('----------test_features.shape：', test_features.shape)
